package com.qifan.alb2b.dao;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.cqcis.base.orm.jdbc.BaseJdbcDao;
import com.cqcis.base.orm.jdbc.JdbcUtil;
import com.qifan.alb2b.domain.TravelInfoDomain;
import com.qifan.alb2b.domain.TravelItemInfoDomain;
import com.qifan.alb2b.domain.UserInfoDomain;
import com.qifan.alb2b.exception.DataAccessException;
import com.qifan.alb2b.util.Page;


/**
 * 
 * Dao基础功能类
 * 功能描述：<br/>
 * 1、封装MySql数据库分页逻辑<br/>
 * 2、封装了增加一个对象的业务逻辑<br/>
 * 3、封装了修改一个对象的业务逻辑<br/>
 * 基于约定大于配置的原则，对于表和java实体转化有如下约定:<br/>
 * 1、将domain类的类名转化为表名
 * 如{@link UserInfoDomain}会转化为[${prefix}][${split}]USERINFO${split}INFO<br/>
 * 其中prefix表示前缀，一般用户表示项目名，如果不提供则不加前缀;split表示分隔符，在静态变量SPLIT的值<br/>
 * 2、将domain类的get方法转化为列名 如createDate会转化为CREATE${split}DATE<br/>
 * 3、将表向domain类转换，同第二条原则，查询请通过调用queryObjByPage方法<br/>
 * 或者直接调用{@link JdbcUtil}的queryForList方法。
 * @author hc24
 * @version 2011-11-17
 */
public class BaseDao extends BaseJdbcDao{	
	private final static Log logger=LogFactory.getLog(BaseDao.class);
	
	/** 分隔符，默认为空 */
	public final static String SPLIT="";
	/** 逗号 */
	public final static String DH=",";
	/** 问号 */
	public final static String WH="?";
	
	
	/**
	 * queryMapByPage的重载方法,sql不包含查询参数
	 * @param sql
	 * @param pageSize
	 * @param curPageNo
	 * @return
	 */
	protected Page queryMapByPage(String sql,int pageSize,int curPageNo) {
		return queryMapByPage(sql, pageSize, curPageNo, null);
	}
	
	/**
	 * 根据查询sql（sql如果包括查询参数采用?号占位符的形式）和分页参数以及查询sql的查询参数得到Page对象
	 * Page对象包括本页的数据和本页的基本属性（总记录数、分页大小，当前页，总页数）
	 * @param sql 查询sql
	 * @param pageSize 分页大小
	 * @param curPageNo  请求的当前页
	 * @param args 包含sql中?号占位符的参数数组,如果没有参数则传递null
	 * @return Page对象里面的result对象是List<Map<String,Object>>的形式
	 */
	protected Page queryMapByPage(String sql,int pageSize,int curPageNo,List<Object> params) {
		if(params==null)
			params=new ArrayList<Object>();
		String fmtStr="%s limit ?,?";
		int totalCount=queryTotalCount(sql, params.toArray());//得到总记录数
		int startNo=Page.getStartOfPage(curPageNo, pageSize);//起始号(包含)
		params.add(startNo);
		params.add(pageSize);
		String querySql=String.format(fmtStr, sql);//构造查询分页sql
		List<Map<String,Object>> results=getJdbcTemplate().queryForList(querySql, params.toArray());
		logger.debug("分页sql:"+querySql+"[params:"+params.toString()+"]");
		Page page=new Page(results, startNo, totalCount, pageSize);
		return page;
	}
	
	/**
	 * queryObjByPage的重载方法,sql不包含查询参数
	 * @param sql
	 * @param pageSize
	 * @param curPageNo
	 * @return
	 */
	protected Page queryObjByPage(String sql,int pageSize,int curPageNo,Class<?> clazzTo) {
		return queryObjByPage(sql, pageSize, curPageNo, null, clazzTo);
	}
	
	/**
	 * 根据查询sql（sql如果包括查询参数采用?号占位符的形式）和分页参数以及查询sql的查询参数得到Page对象
	 * Page对象包括本页的数据和本页的基本属性（总记录数、分页大小，当前页，总页数）
	 * @param sql 查询sql
	 * @param pageSize 分页大小
	 * @param curPageNo  请求的当前页
	 * @param args 包含sql中?号占位符的参数数组,如果没有参数则传递null
	 * @param clazzTo 要转化成的JavaBean对象
	 * @return Page对象里面的result对象是List<T>的形式
	 */
	protected Page queryObjByPage(String sql,int pageSize,int curPageNo,List<Object> params,Class<?> clazzTo) {
		if(params==null)
			params=new ArrayList<Object>();
		String fmtStr="%s limit ?,?";
		int totalCount=queryTotalCount(sql, params.toArray());//得到总记录数
		int startNo=Page.getStartOfPage(curPageNo, pageSize);//起始号(包含)
		params.add(startNo);
		params.add(pageSize);
		String querySql=String.format(fmtStr, sql);//构造查询分页sql
		List<?> results=JdbcUtil.queryForList(this, clazzTo, querySql, params.toArray());
		logger.debug("分页sql:"+querySql+"[params:"+params.toString()+"]");
		Page page=new Page(results, startNo, totalCount, pageSize);
		return page;
	}
	
	/**
	 * 根据查询sql和参数查询总记录数，提供给queryByPage方法调用
	 * @param sql
	 * @param args
	 * @return
	 */
	protected int queryTotalCount(String sql,Object[] args) {
		int fromPoint=sql.indexOf("from");
		String queryCountSql="select count(*) "+sql.substring(fromPoint);
		int count=getJdbcTemplate().queryForInt(queryCountSql, args);
		logger.debug("总数sql:"+queryCountSql+"[count:"+count+"]");
		return count;
	}
	
	/**
	 * 保存一个实体对象,如果保存出错则抛出{@link com.qifan.alb2b.exception.DataAccessException}异常
	 * @param <T> 
	 * @param t 实体对象
	 */
	protected <T> void addObj(T t){
		try{
			/* 通过解析T对象得到表名、所有表字段和值 */
			Class<T> cls=(Class<T>) t.getClass();
			Method[] methods= cls.getMethods();
			//List<Method> getMethods=new ArrayList<Method>();//所有以get开头&排除父类方法&无参数的Method
			String tableName=tranTableName(cls.getName(), "");//表名
			List<String> columnNames=new ArrayList<String>();//所有的表的字段
			List<Object> columnValues=new ArrayList<Object>();//所有表字段值
			for(Method m:methods){
				if(m.getName().startsWith("get")
						&&m.getDeclaringClass().equals(cls)
						&&m.getGenericParameterTypes().length==0){
					//getMethods.add(m);
					columnNames.add(tranColumnName(m.getName()));
					columnValues.add(m.invoke(t));
				}
			}
			
			/* 开始构建insert sql语句 */
			StringBuilder insertSql=new StringBuilder();
			StringBuilder valuesSql=new StringBuilder();
			insertSql.append("insert into ").append(tableName).append("(");
			valuesSql.append(" values(");
			for(int i=0;i<columnNames.size()-1;i++){
				insertSql.append(columnNames.get(i)).append(DH);
				valuesSql.append(WH).append(DH);
			}
			insertSql.append(columnNames.get(columnNames.size()-1)).append(")");
			valuesSql.append(WH).append(")");
			insertSql.append(valuesSql.toString());
			//System.out.println(insertSql.toString()+columnValues.toString());
			this.getJdbcTemplate().update(insertSql.toString(), columnValues.toArray());			
			logger.debug("插入sql："+insertSql.toString()+columnValues.toString());
		}catch(Exception e){
			logger.error("保存"+t+"失败",e);
			throw new DataAccessException(e);
		}
	}
	
	/**
	 * 更新一个实体对象,如果保存出错则抛出{@link com.qifan.alb2b.exception.DataAccessException}异常
	 * ID采用默认的约定TABLENAME_ID
	 * @param <T> 
	 * @param t 实体对象
	 */
	@Deprecated
	protected <T> void updateObj(T t){
		Class<T> cls=(Class<T>) t.getClass();
		updateObj(t, tranTableName(cls.getName(), "")+"_ID");
	}
	
	/**
	 * 更新一个实体对象,如果保存出错则抛出{@link com.qifan.alb2b.exception.DataAccessException}异常
	 * @param <T>
	 * @param t 实体对象
	 * @param idPropName 实体类中代表主键ID的属性名，不能为空
	 */
	protected <T> void updateObj(T t,String idPropName){
		if(idPropName==null||"".equals(idPropName.trim()))
			throw new DataAccessException("更新对象未指定ID属性");
		try{
			/* 通过解析T对象得到表名、所有表字段和值 */
			Class<T> cls=(Class<T>) t.getClass();
			Method[] methods= cls.getMethods();
			//List<Method> getMethods=new ArrayList<Method>();//所有以get开头&排除父类方法&无参数的Method
			String tableName=tranTableName(cls.getName(), "");//表名
			List<String> columnNames=new ArrayList<String>();//所有的表的字段
			List<Object> columnValues=new ArrayList<Object>();//所有表字段值
			String idColumnName=null;
			Object idColumnValue=null;
			for(Method m:methods){
				if(m.getName().startsWith("get")
						&&m.getDeclaringClass().equals(cls)
						&&m.getGenericParameterTypes().length==0){
					if(m.getName().substring(3).equalsIgnoreCase(idPropName)){
						idColumnName=tranColumnName(m.getName());
						idColumnValue=m.invoke(t);
					}else{
						//getMethods.add(m);
						columnNames.add(tranColumnName(m.getName()));
						columnValues.add(m.invoke(t));
					}
				}//end if
			}//end for
			if(idColumnValue==null)
				throw new DataAccessException("指定的ID属性"+idPropName+"在"+cls.getName()+"中不存在或没有对应的get方法");
			columnValues.add(idColumnValue);
			
			/* 开始构建update sql语句 */
			StringBuilder updateSql=new StringBuilder();
			updateSql.append("update ").append(tableName).append(" set "); 
			for(int i=0;i<columnNames.size()-1;i++){
				updateSql.append(columnNames.get(i)).append("=?,");
			}
			updateSql.append(columnNames.get(columnNames.size()-1)).append("=? where ");
			updateSql.append(idColumnName).append("=?");
			
			this.getJdbcTemplate().update(updateSql.toString(), columnValues.toArray());
			logger.debug("更新sql："+updateSql.toString()+columnValues.toString());
		}catch(Exception e){
			logger.error("更新"+t+"失败",e);
			throw new DataAccessException(e);
		}
	}
	
	
	/**
	 * 将domain类的get方法转化为列名
	 * 如createDate会转化为CREATE${split}DATE
	 * @param methodName get方法名，如getCreateDate
	 * @return 列名 如CREATE${split}DATE
	 */
	private String tranColumnName(String methodName){
		String fieldName=methodName.substring(3);
		StringBuilder sb=new StringBuilder();
		String split=SPLIT;
		int preStart=0;
		for(int i=0;i<fieldName.length();i++){
			
			if(fieldName.charAt(i)>=65&&fieldName.charAt(i)<=90&&i!=0){
				sb.append(fieldName.substring(preStart,i)).append(split);
				preStart=i;
			}
		}
		if(preStart==0)
			return sb.append(fieldName).toString().toUpperCase();
		else
			return sb.append(fieldName.substring(preStart)).toString().toUpperCase();
	}
	
	/**
	 * 将domain类的类名转化为表名
	 * 如com.travelsky.tibetair.domain.ConfigInfoDomain会转化为[${prefix}][${split}]CONFIG_INFO
	 * 其中prefix表示前缀，一般用户表示项目名，如果不提供则不加前缀
	 * @param className 类全名 如com.travelsky.tibetair.domain.ConfigInfoDomain
	 * @return 表名
	 */
	private String tranTableName(String className,String prefix){
		String clsName=className.substring(className.lastIndexOf(".")+1);
		String tempName=tranColumnName("get"+clsName.substring(0, clsName.indexOf("Domain")));
		if(prefix!=null&&!"".equals(prefix))
			tempName=prefix+SPLIT+tempName;
		return tempName;
	}
	
	public static void main(String[] args) {
		BaseDao pd=new BaseDao();
		//pd.queryMapByPage("select t.* from xz_config t order by key", 10, 1, new ArrayList<Object>());
		//pd.addObj(new CabinInfoDomain());
		System.out.println('z'+0);
		//pd.addObj(new TravelInfoDomain());
		pd.addObj(new TravelItemInfoDomain());
	}
	
}
